package com.jvm.simulation.hotspot;

import com.jvm.simulation.hotspot.attributes.*;
import com.jvm.simulation.hotspot.bytecode.BytecodeStream;
import com.jvm.simulation.hotspot.descriptor.DescriptorStream2;
import lombok.Data;

@Data
public class MethodInfo {

    InstanceKlass klass;

    AccessFlags accessFlags;

    int methodNameIdx;

    int descriptorIdx;

    String methodName;

    String descriptor;

    int codeAttributeCount;

    CodeAttribute[] codeAttributes;

    private DescriptorStream2 descriptorStream;

    byte[] u2Bytes = new byte[Constants.u2];
    byte[] u4Bytes = new byte[Constants.u4];

    public int setCodeAttributes(byte[] content, int attrCnt, int u2, int idx) {
        codeAttributes = new CodeAttribute[attrCnt];

        for (int i = 0; i < attrCnt; i++) {
            CodeAttribute codeAttribute;
            codeAttributes[i] = codeAttribute = new CodeAttribute();

            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            codeAttribute.setAttributeNameIdx(ByteUtil.byteToUnsignedShort(u2Bytes));
            codeAttribute.setAttributeName((String) klass.getContantPool().getContantPool().get(codeAttribute.getAttributeNameIdx()));

            System.arraycopy(content,idx,u4Bytes,0, Constants.u4);
            idx += Constants.u4;
            codeAttribute.setAttributeLength(ByteUtil.byteToUnsignedShort(u4Bytes));

            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            codeAttribute.setMaxStack(ByteUtil.byteToUnsignedShort(u2Bytes));

            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += Constants.u2;
            codeAttribute.setMaxLocals(ByteUtil.byteToUnsignedShort(u2Bytes));

            System.arraycopy(content,idx,u4Bytes,0, Constants.u4);
            idx += Constants.u4;
            codeAttribute.setCodeLength(ByteUtil.byteArrayToInt(u4Bytes));

            BytecodeStream bytecodeStream = new BytecodeStream(this,codeAttribute);

            codeAttribute.setCode(bytecodeStream);

            System.arraycopy(content,idx,bytecodeStream.getCodes(),0, codeAttribute.getCodeLength());
            idx += codeAttribute.getCodeLength();


            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += 2;
            codeAttribute.setExceptionTableLength(ByteUtil.byteToUnsignedShort(u2Bytes));

            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
            idx += 2;
            int codeCnt;
            codeAttribute.setAttributesCount(codeCnt = ByteUtil.byteToUnsignedShort(u2Bytes));

            idx = praseTable(content,codeCnt,idx,codeAttribute);

        }
        return idx;
    }

    private int praseTable(byte[] content, int codeCnt, int idx, CodeAttribute codeAttribute) {

        for (int i = 0; i < codeCnt; i++) {

            System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
//            idx += Constants.u2;
            String tableName = (String) (klass).getContantPool().getContantPool().get(ByteUtil.byteToUnsignedShort(u2Bytes));

            if (tableName.equals("LineNumberTable")) {
                // table
                LineNumberTable attributeInfo = new LineNumberTable();

                idx = lineNumberTableInfo(content,
                        attributeBaseInfo(content, idx, codeAttribute, tableName, attributeInfo)
                        , attributeInfo
                );
            }else{
                // table
                LocalVariableTable localVariableTable = new LocalVariableTable();

                idx = localVariableTableInfo(content,
                        attributeBaseInfo(content, idx, codeAttribute, tableName, localVariableTable),
                        localVariableTable
                );
            }
        }
        return idx;
    }

    private int localVariableTableInfo(byte[] content, int idx, LocalVariableTable localVariableTable) {
        if (0 != localVariableTable.getTableLength()) {
            for (int j = 0; j < localVariableTable.getTableLength(); j++) {
                LocalVariableTable.Item item = localVariableTable.new Item();

                localVariableTable.getTable()[j] = item;

                // start pc
                System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
                idx += Constants.u2;

                item.setStartPc(ByteUtil.byteToUnsignedShort(u2Bytes));

                // length
                System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
                idx += Constants.u2;

                item.setLength(ByteUtil.byteToUnsignedShort(u2Bytes));

                // name index
                System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
                idx += Constants.u2;

                item.setNameIndex(ByteUtil.byteToUnsignedShort(u2Bytes));

                // descriptor index
                System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
                idx += Constants.u2;

                item.setDescriptorIndex(ByteUtil.byteToUnsignedShort(u2Bytes));

                //index
                System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
                idx += Constants.u2;

                item.setIndex(ByteUtil.byteToUnsignedShort(u2Bytes));
            }
        }
        return idx;
    }

    private int lineNumberTableInfo(byte[] content, int idx, LineNumberTable attributeInfo) {
        if (0 != attributeInfo.getTableLength()) {

            for (int l = 0; l < attributeInfo.getTableLength(); l++) {
                LineNumberTable.Item item = attributeInfo.new Item();

                attributeInfo.getTable()[l] = item;

                // start pc
                System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
                idx += Constants.u2;

                item.setStartPc(ByteUtil.byteToUnsignedShort(u2Bytes));

                // line number
                System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
                idx += Constants.u2;

                item.setLineNumber(ByteUtil.byteToUnsignedShort(u2Bytes));

            }
        }

        return idx;
    }

    private int attributeBaseInfo(byte[] content, int idx, CodeAttribute codeAttribute, String tableName, AttributeInfo attributeInfo) {
        codeAttribute.getAttributes().put(tableName, attributeInfo);

        // attr name index
        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        idx += Constants.u2;
        attributeInfo.setAttrNameIndex(ByteUtil.byteToUnsignedShort(u2Bytes));

        // attr len
        System.arraycopy(content,idx,u4Bytes,0, Constants.u4);
        idx += 4;

        attributeInfo.setAttrLength(ByteUtil.byteArrayToInt(u4Bytes));

        // table length
        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        idx += Constants.u2;

        attributeInfo.setTableLength(ByteUtil.byteToUnsignedShort(u2Bytes));

        attributeInfo.initTable();
        return idx;
    }


    private int attributeBaseInfo(byte[] content, int idx, AttributeInfo attributeInfo) {

        // attr name index
        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        idx += Constants.u2;
        attributeInfo.setAttrNameIndex(ByteUtil.byteToUnsignedShort(u2Bytes));

        // attr len
        System.arraycopy(content,idx,u4Bytes,0, Constants.u4);
        idx += 4;

        attributeInfo.setAttrLength(ByteUtil.byteArrayToInt(u4Bytes));

        // table length
        System.arraycopy(content,idx,u2Bytes,0, Constants.u2);
        idx += Constants.u2;

        attributeInfo.setTableLength(ByteUtil.byteToUnsignedShort(u2Bytes));

        attributeInfo.initTable();
        return idx;
    }

    @Override
    public String toString() {
        return "MethodInfo{ }";
    }

}
